package com.whz.serial;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

/**
 * @ClassName StringList
 * @Description TODO 合理的序列化 StringListReason类
 * 只需先包含链表中字符串的数目,然后紧跟着这些字符串即可
 * 这样就构成了StringList所表示的逻辑数据与它的物理表示细节脱离
 * transient 修饰符表明这个实例域将从一个类的默认序列化形式中省略掉
 * @Author hong-zhi
 * @Date 2021/5/26 17:21
 * @Version 1.0
 */
@Slf4j
public final class StringListReason implements Serializable {

    /**
     * transient 修饰符表明这个实例域将从一个类的默认序列化形式中省略掉
     * 没有transient修饰 Entry必须实现 Serializable 接口
     * 没有transient修饰 size值比实际大一倍 s.writeInt(size);
     */
    private transient int size = 0;
    private transient Entry head;

    /**
     * 内部类不再进行序列化
     */
    private static class Entry {
        String data;
        Entry next;
        Entry previous;

        private Entry(String data, Entry next, Entry previous) {
            this.data = data;
            this.next = next;
            this.previous = previous;
        }

        private Entry(String data, Entry previous) {
            this.data = data;
            this.previous = previous;
        }

        public static Entry create(String data, Entry previous) {
            return new Entry(data, previous);
        }

        @Override
        public String toString() {
            return data;
        }
    }

    /**
     * 添加一个指定的字符串到列表中
     * @param s 指定的字符串
     */
    public final void add(String s) {
        /*尾元素*/

        Entry newEntry = Entry.create(s, null);
        /*列表为空在head处插入元素*/
        if(head == null) {
            head = newEntry;
        } else {
            Entry tail = getTail(head);
            newEntry.previous = tail;
            tail.next = newEntry;
        }
        size ++;
    }

    /**
     *
     * @return
     */
    private Entry getTail(Entry entry) {
        if(entry.next == null) return entry;
        entry = entry.next;
        return getTail(entry);
    }

    /**
     * 对 {@code StringList} 进行序列化
     * @serialData {@code int}字符串列表大小
     *             {@code String} 列表所包含的元素以一个合适的顺序输出
     * @param s 将要使用的输出流
     */
    private void writeObject(ObjectOutputStream s) throws IOException {
        /*当调用defaultWriteObject方法时,每一个未被标记为transient的实例域都会被序列化*/
        s.defaultWriteObject();
        s.writeInt(size);

        // 将所有元素以一个合适的顺序写出
        for (Entry e = head; e != null; e = e.next) {
            s.writeObject(e.data);
        }
    }

    /**
     * 对{@code StringListReason} 进行反序列化
     * @param s 进行操作的输入流
     * @serialData 列表中元素的数量,接着是列表中所有元素以原序排列
     * @throws IOException
     * @throws ClassNotFoundException 若系统无法读取指定文件,将抛出该异常
     */
    private void readObject(ObjectInputStream s)
            throws IOException, ClassNotFoundException {
        s.defaultReadObject();
        int numElements = s.readInt();
        // 将元素插入到列表中
        for (int i = 0; i < numElements; i++) {
            add((String) s.readObject());
        }
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        StringListReason that = (StringListReason) o;
        return size == that.size && com.google.common.base.Objects.equal(head, that.head);
    }

    @Override
    public int hashCode() {
        return com.google.common.base.Objects.hashCode(size, head);
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        Entry cur = head;
        while (cur != null) {
            sb.append(cur);
            sb.append(",");
            cur = cur.next;
        }
        String result = StringUtils.removeEnd(StringUtils.removeStart(sb.toString(), ","), ",");
        return "StringListReason{" +
                "size =" + size +
                ", elements = (" + result +
                ")}";
    }
}
